package sqldriver

import (
	"database/sql"
	"fmt"
	"github.com/mattn/go-sqlite3"
	"io/ioutil"
	"os"
	"path/filepath"
)

var (
	conn             *sqlite3.SQLiteConn
	create           func(username, password string) (file string, err error)
	createWithCrypt  func(username, password, crypt, salt string) (file string, err error)
	connect          func(f string, username, password string) (file string, db *sql.DB, c *sqlite3.SQLiteConn, err error)
	connectWithCrypt func(f string, username, password string, crypt string, salt string) (file string, db *sql.DB, c *sqlite3.SQLiteConn, err error)
	authEnabled      func(db *sql.DB) (exists bool, err error)
	addUser          func(db *sql.DB, username, password string, admin int) (rv int, err error)
	userExists       func(db *sql.DB, username string) (rv int, err error)
	isAdmin          func(db *sql.DB, username string) (rv bool, err error)
	modifyUser       func(db *sql.DB, username, password string, admin int) (rv int, err error)
	deleteUser       func(db *sql.DB, username string) (rv int, err error)
)

func init() {
	// Create database connection
	sql.Register("sqlite3_with_conn",
		&sqlite3.SQLiteDriver{
			ConnectHook: func(c *sqlite3.SQLiteConn) error {
				conn = c
				return nil
			},
		})

	create = func(username, password string) (file string, err error) {
		var db *sql.DB
		file, db, _, err = connect("", username, password)
		db.Close()
		return
	}

	createWithCrypt = func(username, password, crypt, salt string) (file string, err error) {
		var db *sql.DB
		file, db, _, err = connectWithCrypt("", "admin", "admin", crypt, salt)
		db.Close()
		return
	}

	connect = func(f string, username, password string) (file string, db *sql.DB, c *sqlite3.SQLiteConn, err error) {
		conn = nil // Clear connection
		file = f   // Copy provided file (f) => file
		if file == "" {
			// Create dummy file
			//file = TempFilename(t)
		}

		params := "?_auth"
		if len(username) > 0 {
			params = fmt.Sprintf("%s&_auth_user=%s", params, username)
		}
		if len(password) > 0 {
			params = fmt.Sprintf("%s&_auth_pass=%s", params, password)
		}
		db, err = sql.Open("sqlite3_with_conn", "file:"+file+params)
		if err != nil {
			defer os.Remove(file)
			return file, nil, nil, err
		}

		// Dummy query to force connection and database creation
		// Will return ErrUnauthorized (SQLITE_AUTH) if user authentication fails
		if _, err = db.Exec("SELECT 1;"); err != nil {
			defer os.Remove(file)
			defer db.Close()
			return file, nil, nil, err
		}
		c = conn

		return
	}

	connectWithCrypt = func(f string, username, password string, crypt string, salt string) (file string, db *sql.DB, c *sqlite3.SQLiteConn, err error) {
		conn = nil // Clear connection
		file = f   // Copy provided file (f) => file
		if file == "" {
			// Create dummy file
			file = TempFilename()

		}

		db, err = sql.Open("sqlite3_with_conn", "file:"+file+fmt.Sprintf("?_auth&_auth_user=%s&_auth_pass=%s&_auth_crypt=%s&_auth_salt=%s", username, password, crypt, salt))
		if err != nil {
			defer os.Remove(file)
			return file, nil, nil, err
		}

		// Dummy query to force connection and database creation
		// Will return ErrUnauthorized (SQLITE_AUTH) if user authentication fails
		if _, err = db.Exec("SELECT 1;"); err != nil {
			defer os.Remove(file)
			defer db.Close()
			return file, nil, nil, err
		}
		c = conn

		return
	}

	authEnabled = func(db *sql.DB) (exists bool, err error) {
		err = db.QueryRow("select count(type) from sqlite_master WHERE type='table' and name='sqlite_user';").Scan(&exists)
		return
	}

	addUser = func(db *sql.DB, username, password string, admin int) (rv int, err error) {
		err = db.QueryRow("select auth_user_add(?, ?, ?);", username, password, admin).Scan(&rv)
		return
	}

	userExists = func(db *sql.DB, username string) (rv int, err error) {
		err = db.QueryRow("select count(uname) from sqlite_user where uname=?", username).Scan(&rv)
		return
	}

	isAdmin = func(db *sql.DB, username string) (rv bool, err error) {
		err = db.QueryRow("select isAdmin from sqlite_user where uname=?", username).Scan(&rv)
		return
	}

	modifyUser = func(db *sql.DB, username, password string, admin int) (rv int, err error) {
		err = db.QueryRow("select auth_user_change(?, ?, ?);", username, password, admin).Scan(&rv)
		return
	}

	deleteUser = func(db *sql.DB, username string) (rv int, err error) {
		err = db.QueryRow("select auth_user_delete(?);", username).Scan(&rv)
		return
	}
}

// WorkDir 获取程序运行时根目录
func WorkDir() (string, error) {
	execPath, err := os.Executable()
	if err != nil {
		return "", err
	}
	wd := filepath.Dir(execPath)
	//if filepath.Base(wd) == "bin" {
	//	wd = filepath.Dir(wd)
	//}

	return wd, nil
}

func TempFilename() string {
	path, err := WorkDir()
	f, err := ioutil.TempFile(path, "go-sqlite3-test-")
	if err != nil {
		//t.Fatal(err)
		fmt.Println(err)
	}
	f.Close()
	return f.Name()
}

func TestUserAuthEncoders() {
	cases := map[string]string{
		"sha1":    "",
		"ssha1":   "salted",
		"sha256":  "",
		"ssha256": "salted",
		"sha384":  "",
		"ssha384": "salted",
		"sha512":  "",
		"ssha512": "salted",
	}

	for enc, salt := range cases {
		f, err := createWithCrypt("admin", "admin", enc, salt)
		if err != nil {
			//t.Fatal(err)
			fmt.Println(err)
		}
		//defer os.Remove(f) //test

		_, db, _, err := connectWithCrypt(f, "admin", "admin", enc, salt)
		if err != nil {
			fmt.Println(err)
			//t.Fatal(err)
		}
		defer db.Close()
		if e, err := authEnabled(db); err != nil && !e {
			fmt.Println(err)
			//t.Fatalf("UserAuth (%s) not enabled %s", enc, err)
		}
	}
}
